package core.data;
import core.Data;

import java.util.Map;

public class DataHandle<P, D> {
    public final String handle;
    private final Data<?> parent;

    public final DataCollector<P> param = new DataCollector<>();
    public final DataCollector<D> data = new DataCollector<>();

    private final EventActions<DataHandle<P, D>> eventActions = new EventActions<>();

    public DataHandle(String handle, Data<?> data){
        this.handle = handle;
        parent = data;
    }

    public void doEvent(Events onEvent){
        boolean pass = true;
        if(parent.getPaused()){
            pass = false;
            if(eventActions.hasListen()){
                HandleListen<DataHandle<P, D>> action = eventActions.getListen();
                pass = action.apply(this);
            }
        }

        if(pass){
            HandleEvent<DataHandle<P, D>> action = null;
            switch (onEvent){
                case SUCCESS -> {
                    if(eventActions.hasSuccess())
                        action = eventActions.getSuccess();
                }
                case FAILURE -> {
                    if(eventActions.hasFailure())
                        action = eventActions.getFailure();
                }
                case FINISH -> {
                    if(eventActions.hasFinish())
                        action = eventActions.getFinish();
                }
            }
            if(action != null) action.apply(this);
        }
    }

    public boolean getStatus(){
        return parent.getStatus(STATUS.SUCCESS);
    }

    public boolean getStatus(STATUS status){
        return parent.getStatus(status);
    }

    public DataHandle<P, D> setStatus(STATUS status){
        parent.setStatus(status);
        return this;
    }

    public boolean isHandle(String handle){
        return parent.isHandle(handle);
    }

    public boolean hasHandle(String handle){
        return parent.hasHandle(handle);
    }

    @SuppressWarnings("unchecked")
    public <T> Data<T>getRoot(){
        return (Data<T>) parent;
    }

    public DataHandle<P, D> getHandle(){
        return this;
    }

    public <J, K> DataHandle<J, K> getHandle(String handle){
        return parent.getHandle(handle);
    }

    public <J, K> DataHandle<J, K> setHandle(J param, String handle){
        DataHandle<J, K> item = new DataHandle<>(handle, parent);
        item.setParams(param);
        return parent.setHandle(item);
    }
    public <J, K> DataHandle<J, K> setHandle(J param, String handle, HandleTest<J, K> test){
        DataHandle<J, K> item = new DataHandle<>(handle, parent);
        item.setParams(param);
        item.data.onTest(() -> {
            if(test.apply(item)) item.success();
        });
        return parent.setHandle(item);
    }

    public <J> DataHandle<D, J> setHandle(String handle){
        DataHandle<D, J> item = new DataHandle<>(handle, parent);
        item.setParams(getData());
        return parent.setHandle(item);
    }

    public <J> DataHandle<D, J> setHandle(String handle, HandleTest<D, J> test){
        DataHandle<D, J> item = new DataHandle<>(handle, parent);
        item.setParams(getData());
        item.data.onTest(() -> {
            if(test.apply(item)) item.success();
        });
        return parent.setHandle(item);
    }

    public <J> DataHandle<D, J> addHandle(String handle, HandleProcess<D, J> process){
        DataHandle<D, J> item = new DataHandle<>(handle, parent);
        return parent.addHandle(item, () -> {
            item.setParams(getData());
            process.apply(item);
        });
    }

    public <J> DataHandle<D, J> addHandle(String handle, HandleProcess<D, J> process, HandleTest<D, J> test){
        DataHandle<D, J> item = new DataHandle<>(handle, parent);
        item.data.onTest(() -> {
            if(test.apply(item)) item.success();
        });
        return parent.addHandle(item, () -> {
            item.setParams(getData());
            process.apply(item);
        });
    }

    public boolean hasGlobal(String key){
        return parent.hasGlobal(key);
    }

    public <T> T getGlobal(String key){
        return parent.getGlobal(key);
    }

    public DataHandle<P, D> setGlobal(String key, Object value){
        parent.setGlobal(key, value);
        return this;
    }

    public DataHandle<P, D> publish(){
        parent.publish(handle);
        return this;
    }

    public boolean getPaused(){
        return parent.getPaused();
    }

    public DataHandle<P, D> setPaused(boolean pause){
        parent.setPaused(pause);
        return this;
    }

    public DataHandle<P, D> block(HandleEvent<DataHandle<P, D>> action){
        boolean paused = true;
        if(parent.getPaused()){
            paused = false;
        }else{
            parent.setPaused(true);
        }

        action.apply(this);
        if(paused) parent.setPaused(false);
        return this;
    }

    public DataHandle<P, D> block(HandleEvent<DataHandle<P, D>> action, HandleListen<DataHandle<P, D>> listen){
        eventActions.setListen(listen);
        return block(action);
    }

    public DataHandle<P, D> onSuccess(HandleEvent<DataHandle<P, D>> action) {
        eventActions.setSuccess(action);
        return this;
    }

    public DataHandle<P, D> onFailure(HandleEvent<DataHandle<P, D>> action) {
        this.eventActions.setFailure(action);
        return this;
    }

    public DataHandle<P, D> onFinish(HandleEvent<DataHandle<P, D>> action) {
        this.eventActions.setFinish(action);
        return this;
    }

    public void success(){
        parent.setStatus(STATUS.SUCCESS);
        doEvent(Events.SUCCESS);
        doEvent(Events.FINISH);
        parent.next();
    }

    public void success(D value){
        setData(value);
        success();
    }

    public void failure(){
        parent.setStatus(STATUS.FAILURE);
        doEvent(Events.FAILURE);
        doEvent(Events.FINISH);
        parent.next();
    }

    public void failure(String message){
        parent.getError().setMessage(message);
        failure();
    }

    public void failure(Map<String, String> messages){
        parent.getError().setMessage(messages);
        failure();
    }

    public void failure(String key, String message){
        parent.getError().setMessage(key, message);
        failure();
    }

    public P getParams(){
        return param.getValue();
    }

    public DataHandle<P, D> setParams(P value){
        param.setValue(value);
        return this;
    }

    public D getData(){
        return data.getValue();
    }

    public DataHandle<P, D> setData(D value){
        data.setValue(value);
        return this;
    }

    public DataError getError(){
        return parent.getError();
    }

    public String getMessage(){
        return parent.getError().getMessage();
    }

    public Map<String, String> getMessages(){
        return parent.getError().getMessages();
    }

    public DataHandle<P, D> setMessage(String message){
        parent.getError().setMessage(message);
        return this;
    }

    public DataHandle<P, D> setMessage(Map<String, String> messages){
        parent.getError().setMessage(messages);
        return this;
    }

    public DataHandle<P, D> setMessage(String key, String message){
        parent.getError().setMessage(key, message);
        return this;
    }

    public Integer getCode(){
        return parent.getCode();
    }

    public DataHandle<P, D> setCode(int code){
        parent.setCode(code);
        return this;
    }

    public DataResult getResult(){
        DataResult result;
        if(parent.getStatus(STATUS.SUCCESS)){
            result = new SuccessResult(data.getValue(), parent.getCode());
        }else{
            result = new FailureResult(getMessages(), parent.getCode());
        }
        return result;
    }

    public void next(){
        parent.next();
    }

    public DataHandle<P, D> start(){
        parent.start();
        return this;
    }

    @SuppressWarnings("unchecked")
    public <T> DataHandle<P, D> start(HandleEvent<Data<T>> action){
        ((Data<T>)parent).start(action);
        return this;
    }
}
